package com.ubb.dbms.core.parser.visitors;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.statement.StatementVisitor;
import net.sf.jsqlparser.statement.create.table.ColumnDefinition;
import net.sf.jsqlparser.statement.create.table.CreateTable;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.drop.Drop;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.replace.Replace;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.truncate.Truncate;
import net.sf.jsqlparser.statement.update.Update;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.ubb.dbms.core.Utils.SQLExceptions.ConstraintException;
import com.ubb.dbms.core.Utils.SQLExceptions.IndexKeyNullException;
import com.ubb.dbms.core.Utils.SQLExceptions.ItemAlreadyExistsException;
import com.ubb.dbms.core.Utils.SQLExceptions.ItemNotExistsException;
import com.ubb.dbms.core.Utils.SQLExceptions.SQLExcepion;
import com.ubb.dbms.core.connection.result.MessageResult;
import com.ubb.dbms.core.parser.SQLQueryParser;
import com.ubb.dbms.domain.queryData.DeleteElements;
import com.ubb.dbms.domain.queryData.TableRowElements;
import com.ubb.dbms.domain.xml.FkAttribute;
import com.ubb.dbms.domain.xml.ForeignKey;
import com.ubb.dbms.domain.xml.ForeignKeys;
import com.ubb.dbms.domain.xml.PkAttribute;
import com.ubb.dbms.domain.xml.PrimaryKey;
import com.ubb.dbms.domain.xml.RefAttribute;
import com.ubb.dbms.domain.xml.RefTable;
import com.ubb.dbms.domain.xml.References;
import com.ubb.dbms.domain.xml.Structure;
import com.ubb.dbms.domain.xml.Table;
import com.ubb.dbms.domain.xml.TableAttribute;

public class SQLStatementVisitor implements StatementVisitor {
	private final Logger log = LoggerFactory.getLogger(SQLStatementVisitor.class);

	private boolean hasConstraint(List<String> columnSpecStrings, String c1, String c2) throws SQLExcepion {
		columnSpecStrings = toUpperCase(columnSpecStrings);
		final int c1Ind = columnSpecStrings.indexOf(c1);
		try {
			if (c1Ind != -1) {
				if (columnSpecStrings.get(c1Ind + 1).equals(c2))
					return true;
				else
					throw new ConstraintException();
			}
		} catch (IndexOutOfBoundsException e) {
			throw new ConstraintException();
		}
		return false;
	}
	
	private boolean hasConstraint(List<String> columnSpecStrings, String c1) {
		columnSpecStrings = toUpperCase(columnSpecStrings);
		if (columnSpecStrings.get(0).equals(c1))
			return true;
		return false;
	}
	
	private List<String> toUpperCase(List<String> strings) {
		List<String> newList = new ArrayList<String>();
	    for (String s: strings)
	        newList.add(s.toUpperCase());
	    return newList;
	}
	
	private ForeignKey createForeignKey(String sFkAttribute, String sRefTable, String sRefAttribute) {
		FkAttribute fkAttribute = new FkAttribute();
		fkAttribute.setValue(sFkAttribute);
		List<FkAttribute> fkAttributeList = new ArrayList<FkAttribute>();
		fkAttributeList.add(fkAttribute); // only one
		
		RefTable refTable = new RefTable();
		refTable.setValue(sRefTable);
		
		RefAttribute refAttribute = new RefAttribute();
		refAttribute.setValue(sRefAttribute);
		List<RefAttribute> refAttributeList = new ArrayList<RefAttribute>();
		refAttributeList.add(refAttribute); // only one
		
		References references = new References();
		references.setRefTable(refTable);
		references.setRefAttributelList(refAttributeList);
		
		ForeignKey foreignKey = new ForeignKey();
		foreignKey.setFkAttributeList(fkAttributeList);
		foreignKey.setReferences(references);
		
		return foreignKey;
	}

	@Override
	public void visit(CreateTable createTable) {
		Table table = new Table();
		String tableName = createTable.getTable().getName();
		table.setTableName(tableName);
		log.info("Parsed Table name: " + tableName);
		
		Structure structure = new Structure();
		table.setStructure(structure);
		List<TableAttribute> tableAttributeList = new ArrayList<TableAttribute>();
		structure.setAttributeList(tableAttributeList);
		
		PrimaryKey primaryKey = new PrimaryKey();
		table.setPrimaryKey(primaryKey);
		List<PkAttribute> pkAttributeList = new ArrayList<PkAttribute>();
		primaryKey.setPkAttribute(pkAttributeList);
		
		List<ForeignKey> foreignKeyList = new ArrayList<ForeignKey>();
		ForeignKeys foreignKeys = new ForeignKeys();
		foreignKeys.setForeignKeyList(foreignKeyList);
		
		List<ColumnDefinition> columnDefinitions = new ArrayList<ColumnDefinition>(createTable.getColumnDefinitions());
		for (ColumnDefinition columnDef : columnDefinitions) {
			TableAttribute tableAttribute = new TableAttribute();
			
			final String attributeName = columnDef.getColumnName();
			tableAttribute.setAttributeName(attributeName);
			log.info("Parsed Attribute name: " + attributeName);

			final String type = columnDef.getColDataType().getDataType();
			tableAttribute.setType(type);
			log.info("Parsed Attribure type: " + type);
			
			try { 
				// check only the first argument -> length (no type with multiple argument needed)
				if (columnDef.getColDataType().getArgumentsStringList().size() > 0) {
					final String length = columnDef.getColDataType().getArgumentsStringList().get(0).toString();
					tableAttribute.setLength(length);
					log.info("Parsed Attribute length: " + length);
				}
			} catch (NullPointerException e) {
				// no length added to attribute if not defined
				log.info("No Attribute length defined");
			}
			
			try {
				// Constraints (only for NOT NULL, PRIMARY KEY) 
				List<String> columnSpecStrings = new ArrayList<String>(columnDef.getColumnSpecStrings());
				try {
					if (hasConstraint(columnSpecStrings, "NOT", "NULL")){
						tableAttribute.setIsNull("false");
						log.info("Parsed Attribute isnull: " + "false");
					}
					
					if (hasConstraint(columnSpecStrings, "PRIMARY", "KEY")) {
						PkAttribute pkAttribute = new PkAttribute();
						pkAttribute.setValue(attributeName);
						pkAttributeList.add(pkAttribute);
						log.info("Parsed PRIMARY KEY for Attribute: " + attributeName);
					}
					
					if (hasConstraint(columnSpecStrings, "REFERENCES")) {
						String nonSplitRefData;
						String parsedTableName = "", parsedColumnName = "";
						if (columnSpecStrings.size() == 3) {
							nonSplitRefData = columnSpecStrings.get(2).replaceAll("\\(|\\)", " ");
							String[] parsedData = nonSplitRefData.trim().split("\\s");
							if (parsedData.length == 1) {
								parsedTableName = columnSpecStrings.get(1);
								parsedColumnName = parsedData[0];
							} else {
								throw new ConstraintException();
							}
						} else {
							throw new ConstraintException();
						}
						ForeignKey foreignKey = createForeignKey(columnDef.getColumnName(), parsedTableName, parsedColumnName);
						foreignKeyList.add(foreignKey);
					}
				} catch (SQLExcepion e) {
					log.error("Invalid Constraints list: " + columnSpecStrings.toString());
					MessageResult.sendMessage(MessageResult.MESSAGE_ERROR, "Invalid Constraints list: " + columnSpecStrings.toString());
					return;
				}
				
			} catch (NullPointerException e) {
				log.info("No Constraints defined");
			}
			
			tableAttributeList.add(tableAttribute);
		}
		if (!foreignKeyList.isEmpty())
			table.setForeignKeys(foreignKeys);
			
		// calling method of operatorEvaluator with "table" domain object
		try {
			log.info("CREATE TABLE");
			SQLQueryParser.getOperatorEvaluator().manageCreateTable(table, SQLQueryParser.getDatabaseName());
			MessageResult.sendMessage(MessageResult.MESSAGE_INFO, "Table created");
		} catch (ItemAlreadyExistsException e) {
			log.error("Table already exists");
			MessageResult.sendMessage(MessageResult.MESSAGE_INFO, "Table already exists");
		}
		
		
	}
	
	@Override
	public void visit(Insert insert) {
		Map<String, Object> insertData = new HashMap<String, Object>();
		ExpressionList values = (ExpressionList) insert.getItemsList();
		for (int i = 0; i < insert.getColumns().size(); i++) {
			insertData.put(insert.getColumns().get(i).toString(), values.getExpressions().get(i).toString());
		}
		
		TableRowElements rowElements = new TableRowElements();
		rowElements.setColumnValues(insertData);
		
		try {
			log.info("INSERT ROW");
		
			// insert.getTable() from jsqlParser is different from this project's Table
			SQLQueryParser.getOperatorEvaluator().manageInsertRow(rowElements, insert.getTable().getName(), SQLQueryParser.getDatabaseName());
			MessageResult.sendMessage(MessageResult.MESSAGE_INFO, "Row inserted");
		} catch (ItemNotExistsException e) {
			log.error("Table not exists");
			MessageResult.sendMessage(MessageResult.MESSAGE_ERROR, "Table not exists");
		} catch (IndexKeyNullException e) {
			log.error("IndexKeyNullException exception in SQLStatementVisitor.visit(Insert insert) method.", e);
		}
	}
	
	@Override
	public void visit(Update update) {
		// TODO Auto-generated method stub
		
	}
	
	@Override
	public void visit(Delete delete) {
		DeleteElements deleteElements = new DeleteElements();
		deleteElements.setTableName(delete.getTable().getName());
		// set conditions
		if (delete.getWhere() != null) {
			SQLExpressionVisitor expressionAndConditionVisitor = new SQLExpressionVisitor();
			delete.getWhere().accept(expressionAndConditionVisitor);
			deleteElements.setCondition(expressionAndConditionVisitor.getConditions());
		
			// set logicalOperator
			deleteElements.setLogicalOperator(expressionAndConditionVisitor.getLogicalOperator());
		}
		
		try {
			SQLQueryParser.getOperatorEvaluator().manageDelete(deleteElements, SQLQueryParser.getDatabaseName());
		} catch (ItemNotExistsException e) {
			log.error("Table not exists");
			MessageResult.sendMessage(MessageResult.MESSAGE_ERROR, "Table not exists");
		}
	}
	
	@Override
	public void visit(Select select) {
		select.getSelectBody().accept(new SQLSelectVisitor());
	}
	
	@Override
	public void visit(Replace arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void visit(Drop drop) {
		if ("database".equals(drop.getType())) {
			SQLQueryParser.dropDatabaseQuery(drop.getName());
		}
	}

	@Override
	public void visit(Truncate arg0) {
		// TODO Auto-generated method stub

	}

}
